RAM unter dem ROM

← Zurück zum Forum

Hallo,

ich wollte mal den RAM unter dem OS-ROM (Speicherbereich $c000-$cfff und $e000-$ffff) auf dem 800XL/XE nutzen.

Da ja das OS den VBL initialisiert muss man den ausschalten, kann dann das RAM unter dem ROM einblenden und dann für eigene Zwecke nutzen. Das funktioniert auch bestens (s. Beispielcode unten)

Nur, wie lade ich den RAM-Bereich Programm oder Daten?

.org funktioniert in dem Bereich ja nicht, weil zum Zeitpunkt des Ladens ja das OS noch angeschaltet ist.

  run start         

  org $0600 ; Page 6 fuer diesen Test genommen

start

wait 
  lda $14   ; rtclock-Warten, damit der Interrupt nicht gerade reinrauscht, beim Abschalten
  cmp $14
  beq wait

  ;VBI-Interrupt aus
  sei
  lda #0
  sta $d40e

  ; ROM aus
  lda #$fe
  sta $d301

  ; spasseshalber den Farbwert für Rot in die Speicheradresse $e000 schreiben

  lda #52
  sta $e000

  ; Dann wieder dort auslesen
  lda $e000
  ; und damit die Hintergrundfarbe setzen
  sta $2c8

  ; nun das ROM wieder einschalten
  lda #$ff
  sta $d301

  ; jetzt auch wieder den VBI-Interrupt wieder einschalten
  lda #$40
  sta $d40e
  cli


endloop jmp endloop 
AntwortZitat

So. Muss mich mal selbst zitieren, da ich es gerade hinbekommen habe. Man muss das Schlüsselwort INI benutzen, das eine Routine startet, bevor der Rest des Programmes geladen wird.

Hier ein kleines Beispielprogramm, das im Speicherbereich des ROMs Programmcode hinterlegt, der den Farbwert des Bildschirmrahmens ändert (zunächst rot, dann auf Joystickknopf weiss).

Das Progrämmchen schaltet in der INI-Routine auch gleich noch das BASIC weg, damit braucht der Anwender nicht mehr OPTION beim Anschalten des ATARIs gedrückt halten.

Der Quelltext (MADS Assembler) sollte selbsterklärend sein.

Nun ist die nächste Frage, was ich tun muss, damit DLI und VBI auch noch funktionieren, wenn das RAM unter dem ROM eingeschaltet ist?

color4    equ $02c8 ; Farbe für den Rahmen oder Hintergrundfarbe (je nach Bildschirmmodus)

nmien   equ $d40e ; Steuerregister für die nicht maskierbaren Interrupts

portb   equ $d301 ; schaltet BASIC aus $a000-$bfff und RAM unter dem ROM ein $c000-$cfff + $e000-$ffff  

rtclok    equ $12

trig0   equ $d010 ; Joystickknopf Joystick0

  org $2000 

initialize
  lda portb
  ora $2      ; BASIC ausschalten
  sta portb

wait  lda rtclok+2    ; rtclock-Warten, damit der Interrupt nicht gerade reinrauscht, beim Abschalten
  cmp rtclok+2    ; denn rtclock wird bei jedem VBI um eins erhöht
  beq wait

  ;VBI-Interrupt aus
  sei
  lda #0
  sta nmien

  ; ROM aus
  lda #$fe
  sta portb
  rts

        ini initialize    ; Dies bewirkt, dass dieser Programmcode ausgeführt wird, bevor der Rest des Programms
        ; geladen wird. 
        ; Dies hat den Vorteil, daß die Speicherbereiche, die 
        ; standardmäßig für Anwenderprogramme gesperrt sind (BASIC und OS)
        ; freigegeben werden können und dann die entsprechenden Programmteile dorthin
        ; geladen werden
        ; $a000-$bfff (BASIC)
        ; $c000-$cfff (OS) bzw. $e000-$ffff (OS)


  run start         

sub_rom_aus
  lda rtclok+2    ; rtclock-Warten, damit der Interrupt nicht gerade reinrauscht, beim Abschalten
  cmp rtclok+2
  beq wait

  ;VBI-Interrupt aus
  sei
  lda #0
  sta nmien

  ; ROM aus
  lda #$fe
  sta portb
  rts

sub_rom_ein
  lda #$ff
  sta portb

  ; jetzt auch den VBI-Interrupt wieder einschalten
  lda #$40
  sta nmien
  cli

  rts

sub_triggerabfrage ;joystickfeuer gedrückt?
joystickloop lda trig0
  cmp #1
  beq joystickloop

  rts

start
  ; Als erstes das ROM einschalten
  jsr sub_rom_ein

  ; Hier fehlt noch das eigentliche Programm, das über diesen Test hinausgeht
  ; ...
  ;-------------------------------------------------

  ; Vor dem Aufruf der Routine im RAM unter dem ROM, ROM ausschalten
  jsr sub_rom_aus 
  jsr sub_ramunterrom_rot
  ; Nach dem Aufruf der Routine im RAM unter dem ROM, ROM wieder einschalten
  jsr sub_rom_ein

  jsr sub_triggerabfrage

  ; Vor dem Aufruf der Routine im RAM unter dem ROM, ROM ausschalten  
  jsr sub_rom_aus
  jsr sub_ramunterrom_weiss
  ; Nach dem Aufruf der Routine im RAM unter dem ROM, ROM wieder einschalten
  jsr sub_rom_ein

endloop jmp endloop 

  org $e000

sub_ramunterrom_rot
  ; Farbe Rot
  lda #$36
  ; und damit die Rahmen-/Hintergrundfarbe setzen
  sta color4
  rts

sub_ramunterrom_weiss
  ; Farbe weiss
  lda #$0f
  ; und damit die Rahmen-/Hintergrundfarbe setzen
  sta color4
  rts 
AntwortZitat

Hallo in die Runde, ich wollte aus eigenem gegebenem Anlass das Thema RAM unter dem ROM / OS hier nochmal aufwärmen, weil ich glaube, daß dieses sehr interessante Thema noch nicht vollständig für die Allgemeinheit dargestellt wurde. Ich persönlich nutze ja das Bank Switching und bin mit meinem Hauptprogramm jetzt fast am Ende des Speichers, müsste jetzt also so langsam Hauptprogrammcode in einer andere Bank verlaagern - ca. 8kB mehr Speicher durch unnützen ROM wäre natürlich viel besser.

Nun gab es hier m Forum schon ein paar tolle und teilweise auch umfassende Lösungen, für seine Zwecke perfekt umgesetzt hat es aber wohl mein Freund Janko - zumindest genau so finde ich es persönlich optimal:

  1. Das OS wird dauerhaft abgeschaltet während das Spiel (Programm) läuft.
  2. Der interne VBI wird eigens neu geschrieben und kümmert sich um wichtige Aufgaben wie RTCLOK, bestimmte Register (DList), evt. eigene Schattenregister, Joystickabfragen, Chbas usw. Im Grunde (so habe ich es verstanden) gibt es dann auch nur noch einen einzigen VBI - der eigene neue interne und das, was man ursprünglich in den eigenen VBI coden will.
  3. Das OS wird wahlweise für bestimmte Operationen wieder angeschaltet (Tastatur, Break, Highscore speichern...).

Wie man das OS ein- und ausschaltet hat Lars in seinem Beitrag sehr schön gezeigt. Eine eigene VBI-Routine hat Janko für sich entwickelt... hier an dieser Stelle würde ich das gerne zusammenführen - so, daß es für jeden einfach einsetzbar ist.

Ich würde das gerne hier im Forum unter MADS/Eclipse entwickeln und ein komplettes lauffähiges kleines Demoprogramm daraus machen.

Das Programm soll dann folgendes können:

Ein einfacher Player kann mittels Joystick in x-Achse bewegt werden, das Hauptprogramm startet dabei bei $2000, die Playerdaten liegen in Bank1 und die Playerbase fängt im RAM unter dem ROM an. Zusätzlich haben wir eine einfache Displaylist mit einem DLI, der die Hintergrundfarbe ändert.

Ich (wir) werden das Programm hier komplett entwickeln - Schritt für Schritt. Jeder ist herzlichst aufgerufen hier rege mitzuarbeiten. Am Ende stelle ich das alles zusammen und daraus machen wir für das nächste Magazin noch einen guten Beitrag draus!

Gruß

Peter

AntwortZitat

Hallo!

Da jeder einen anderen Kenntnisstand hat, will ich einfach einmal von vorne anfangen 🙂

Das RAM unter dem OS kann man genauso wie das RAM unterm BASIC einfach über PORTB einschalten, indem man den ROM-Chip abschaltet. Es ist somit keinerlei Hexenwerk das RAM unterm OS zu nutzen...

Jedoch wird der ATARI sich aufhängen, wenn man einfach das OS abschaltet, da der ATARI einen Non Maskable Interrupt (NMI) und einen Maskable Interrupt (IRQ) besitzt und diese ohne dem OS ins Nirvana verzweigen und somit den ATARI abstürzen lassen.

Was sind das für Interrupts?

Der NMI kommt ausschließlich vom ANTIC. Der ANTIC signalisiert über NMIEN, ob es durch einen VBI oder DLI ausgelöst wurde. Der IRQ kommt vom POKEY und vom PIA. Man kann über IRQST des POKEY oder PACTL des PIA auslesen aus welchem Grund ein IRQ ausgelöst wurde. Der POKEY ist u.a. für die Tastatur zuständig. Ein Tastendruck löst somit den IRQ aus.

Den IRQ kann man einfach per CPU-Flag unterdrücken. "SEI" lässt den IRQ unterdrücken. Mir "CLI" wird dieser wieder berücksichtigt. Der NMI lässt sich nicht ausschalten... zu mindestens nicht CPU-seitig. Hier kann man aber dem ANTIC über NMIRES mitteilen, dass man keine IRQs wünscht. Sind die beide IRQs abgeschaltet, kann man den RAM unterm ROM nutzen...

Nun ja, jetzt funktioniert aber nicht mehr viel. Keine Tastatureingaben, kein VBI und kein DLI. Für Spiele ist der VBI und DLI schon sehr wichtig. Und auch die Tastatur wird immer Mal wieder benötigt. Wichtig zu wissen: Funktionstasten und Joystick benötigen keinen IRQ und funktionieren weiterhin.

Woher weiß eigentlich die CPU, was sie machen soll, wenn ein NMI oder IRQ auftritt? An den Speicherstellen $FFFA/$FFFB steht die Sprungmarke für den NMI. An $FFFE/$FFFF die Sprungmarke für den IRQ. $FFFC/$FFFD ist beim ATARI 400/800 für den Reset zuständig. Diese weisen beim eingeschaltetem OS auf die entsprechenden ROM-Routinen. Ist das OS abgeschaltet verweisen diese ins Nirvana.

Es gibt ein Assemblerlisting vom TurboBasic, wie dort mit dem Problem umgegangen wird. Es ist eigentlich sehr elegant. TB hat zwei eigene Routinen für IRQ und NMI, die im Prinzip den Stack ein wenig manipulieren, damit das ROM zu der eigenen Routine zurück kehrt. Schaltet das OS ein, springt zur gewünschten ROM-Routine, schaltet das OS wieder aus und beendet den Interrupt sozusagen.

Wenn man dauerhaft den RAM unterm OS nutzen möchte, muss man entweder die TB-Routine nutzen, oder sich etwas Eigenes basteln. Für mein Spiel benötige ich die meisten Schattenregister nicht, sondern nur ein paar (für mich) wichtige Funktionen des System-VBI. Deshalb habe ich mir aus dem OS die interessanten Funktionen in eine eigene Routine kopiert:

; --- MADS Assembler-Routine
    .PROC MyNMI
    BIT NMIST   ; Prüfen ob ein DLI ausgelöst wurde
          BPL @+
          JMP (VDSLST)    ; Wenn ja, dann ab in die DLI Routine

@           PHR

    INC RTCLOK+2    ; Systemtimer weiterlaufen lassen
    BNE @+
    INC RTCLOK+1
    BNE @+
    INC RTCLOK
@   LDA #$00
    STA SRTIMR
    MVA CHBAS  CHBASE ; Zeichensatz Schattenregister umkopieren
    MWA SDLSTL DLISTL ; DLI-Schattenregister umkopieren
    MWA SDMCTL DMACTL ; DMA-Kontroll-Schattenregister umkopieren
    LDA PORTA   ; Joystick Port A ist Stick 0 & 1
    LSR     ; Diese trennen und in die passenden 
    LSR     ; Schattenregister bringen
    LSR
    LSR
    STA STICK1
    LDA PORTA
    AND #$0F
    STA STICK0
    LDA TRIG0   ; Feuerknopf ebenfalls im jeweiligen
    STA STRIG0    ; Schattenregister kopieren
    LDA TRIG1
    STA STRIG1
    LDX #$03    ; Die einzelnen Paddles in die Schattenregister
@   LDA POT0,X    ; umkopieren
    STA PADDL0,X
    DEX
    BPL @-

    PLR
    RTI
    .ENDP

Welchen Vorteil bringt mir dieses Vorgehen? Ich spare ein wenig Rechenzeit im VBI und kann damit mehr eigenen Programmcode im "nichtsichtbaren" Bereich ausführen lassen. Den IRQ brauche ich größtenteils nicht und kann diesen per SEI abschalten. Aber sollte man doch z.B. die Tastatur nutzen wollen, kann man einfach die Routine des TurboBasics nehmen (leicht abgewandelt):

; --- MADS Assembler-Routine
    .PROC MyIRQ
    PHA     ; Akku sichern
          LDA #>Exit    ; Eigene Exit-Routine in den Stack schreiben
          PHA
          LDA #<Exit
          PHA
          PHP     ; Prozessorstatus auf den Stack (für RTI)
          INC PORTB   ; OS-ROM anschalten (wichtig OS-ROM muss vorher aus sein!)
          JMP (VIMIRQ)    ; Ins Betriebssystem springen

Exit    DEC PORTB   ; OS-ROM wieder abschalten
          PLA     ; Akku holen
          RTI       ; und zurück
          .ENDP

Um die Sprungadressen an $FFFA-$FFFF sicher setzen zu können, sollte man vorher kurz die Interrupts abschalten:

; --- MADS Assembler-Routine
    SEI       ; IRQ und VBI deaktivieren
    LDA #0
    STA NMIEN

    LDA PORTB     ; OS-ROM und BASIC abschalten
    AND #%11111100
    ORA #%00000010      ; bit 0==0 OS-RAM bit 1==1 BASIC-RAM
    STA PORTB

    LDX #0        ; Neue IRQ-Sprungadressen setzen
POINTER_LOOP  LDA NEWPOINTER,X
    STA $FFFA,X
    INX
    CPX #6
    BNE POINTER_LOOP    

    LDA #NMIEN_VBI      ; VBI und IRQ wieder aktivieren
    STA NMIEN
    CLI

    ...
    ...

NEWPOINTER  .WORD MyNMI
    .WORD 0
    .WORD MyIRQ   
    .ENDP

Jetzt kann man gefahrlos das OS dauerhaft im Programm abschalten, um den RAM nutzen zu können. Je nachdem ist es ggf. sinnvoll den Zeichensatz aus dem ROM zu kopieren, sofern man keinen eigenen hat.

Grüße Janko

Mein 600XL ist virenfrei!

AntwortZitat

Kveldulfur schrieb: $FFFC/$FFFD ist beim ATARI 400/800 für den Reset zuständig.

Moin!

Jetzt ist mir in der Nacht eingefallen, dass ich einen Fehler gemacht habe, kann ihn aber nicht korrigieren 😳 Die Adresse $FFFC/$FFFD ist der Reset-Vektor (auch beim XL/XE). Nur kann man diesen nicht verändern, da nach einem Reset automatisch das OS wieder beim XL/XE aktiv ist.

Grüße Janko

Mein 600XL ist virenfrei!

AntwortZitat

Danke für die umfassenden Vorarbeiten zu diesem Thema (Lars und Janko) - mit der schönen eigenen VBI Routine von Janko und der IRQ Routine von Lars, kann man nun relativ einfach den RAM unter dem ROM/OS nutzen. Anbei habe ich mal versucht in einer kleinen lauffähigen Demo das so umzusetzen, daß es jeder verstehen und für sich nutzen kann. Ich habe dabei versucht möglichst die speziellen Eigenheiten von MADS nicht zu nutzen - außer MVA und MWA. MVA speicher1 speicher2. bedeutet. nur lda speicher1 sta speicher2 Bei MWA wird dann automatisch noch lda speicher1+1 sta speicher2+1 gemacht. phr und plr bedeuten: phr -> pha plr -> pla -> txa -> tay -> pha -> pla -> tya -> tax -> pha -> pla

Ansonsten sollte das Programm folgendes können:

Ein vbi ändert ständig die Hintergrundfarbe. Zwei dli teilen die Farbe des Bildschirmes. Das Hauptprogramm stellt per Random Charakter auf dem Bildschirm dar. Der Bildschirm liegt im RAM unter dem ROM (hier bei E000) - das habe ich zu Demozwecken so gewählt, in der Praxis würde ich hier was anderes reinmachen - z.B. PM-Grafik usw. Wichtig noch: Ganz am Anfang kopiere ich den Zeichensatz runter auf $1000 - der ist nämlich weg, wenn das OS aus ist. RAM unter ROM zu nutzen bringt ca. 14 kB zusätzlich nutzbaren Speicher, was erheblich ist. Man hat aber auch einiges zu beachten: Wichtige Schattenregister muss man selber in die Hardwareregister (über den eigenen VBI) schreiben. Hier kann man natürlich auch seine eigenen Schattenregister nehmen. Mehr Kontrolle - mehr Pflichten!!

Anbei der Code:

org $2000


display = $e6
charak  = $e8
charakn = $ea

vdslst = $200
nmien = $d40e
portb = $d301
porta = $d300
wsync = $d40a
colpf2 = $d018
color2 = $2c6
nmist = $d40f
rtclok = $12
srtimr = $228
chbas = $2f4
chbase = $d409
sdlstl = $230
dlistl = $d402
sdmctl = $22f
dmactl = $d400
stick0 = $278
stick1 = $279
trig0 = $d010
trig1 = $d011
strig0 = $0284
strig1 = $0285
pot0 = $d200
paddl0 = $270
colbk = $d01a
vimirq = $216
nmien_dli = $80
nmien_vbi = $40
random = $d20a
chactl = $d401
chact = $2f3

backgr    .by 00

vektoren  .word vbinter
      .word 0
      .word meinirq   


      .proc main
      ;--------------------Zeichensatz umkopieren auf $1000
      mva chbas charak+1  
      mva #$10 charakn+1
      lda #0 
      sta charak
      sta charakn
      ldy #0
      ldx #0
      clc
lp1     lda (charak),y
      sta (charakn),y
      iny
      cpy #255
      bne lp1
      ldy #0
      inc charakn+1
      inc charak+1
      inx
      cpx #4
      clc
      bne lp1


      mwa #$e000 display  ;Bildschirmstart bei $E000
      mwa #dlist sdlstl ;Displaylist wird initialisiert
      mva #$10 chbas    ;Zeichensatz auf $1000 biegen
      mwa #dli1 vdslst  ;1.DLI Vektor setzen  

      sei         ; IRQ und VBI deaktivieren
      lda #0
      sta nmien

      lda portb     ; OS-ROM und BASIC abschalten
      and #%11111100
      ora #%00000010    ; bit 0==0 OS-RAM bit 1==1 BASIC-RAM
      sta portb

      ldx #0        ; Neue IRQ-Sprungadressen setzen
lp2     lda vektoren,x
      sta $fffa,x
      inx
      cpx #6
      bne lp2   

      lda #nmien_vbi + nmien_dli  ; VBI und IRQ wieder aktivieren
      sta nmien
      cli
      mva chact chactl  ; Darstellung der Character

      ;--------- kurzes Hauptprogramm ---------
loop    iny
      lda random
      sta (display),y

      jmp loop      
      ;--------   Ende Hauptprogramm ----------

      .endp

      ; ab hier stehen die Subroutinen

      .proc dli1      ; erster DLI
        phr 
        sta wsync
        mva #222 colpf2
        mwa #dli2 vdslst  ; Ende dli1 initialisiert dli2
        plr
        rti
      .endp
      .proc dli2
        phr 
        sta wsync
        mva #55 colpf2
        mwa #dli1 vdslst  ; Ende dli2 initialisiert dli1
        plr
        rti
      .endp
      .proc vbinter
      bit nmist     ; Prüfen ob ein DLI ausgelöst wurde
          bpl lp3
          jmp (vdslst)    ; Wenn ja, dann ab in die DLI Routine

lp3         phr   
      inc rtclok+2    ; Systemtimer weiterlaufen lassen
      bne lp4
      inc rtclok+1
      bne lp4
      inc rtclok
lp4     lda #$00
      sta srtimr
      mva chbas  chbase ; Zeichensatz Schattenregister umkopieren
      mwa sdlstl dlistl ; DLI-Schattenregister umkopieren
      mwa sdmctl dmactl ; DMA-Kontroll-Schattenregister umkopieren
      mva color2 colpf2
      lda porta     ; Joystick Port A ist Stick 0 & 1
      lsr         ; Diese trennen und in die passenden 
      lsr         ; Schattenregister bringen
      lsr
      lsr
      sta stick1
      lda porta
      and #$0F
      sta stick0
      lda trig0     ; Feuerknopf ebenfalls im jeweiligen
      sta strig0      ; Schattenregister kopieren
      lda trig1
      sta strig1
      ldx #$03      ; Die einzelnen Paddles in die Schattenregister
lp5     lda pot0,x      ; umkopieren
      sta paddl0,x
      dex
      bpl lp5

      inc backgr      ; der VBI ändert die Farbe des Hintergrundbildschirmes
      mva backgr colbk  ; ein Schattenregister kann incrementiert werden
                ; ein Hardwareregister nicht (nur schreiben)
      plr
      rti
      .endp
      .proc meinirq

      pha       ; Akku sichern
          lda #>ex    ; Eigene Exit-Routine in den Stack schreiben
          pha
          lda #<ex
          pha
          php       ; Prozessorstatus auf den Stack (für RTI)
          inc portb   ; OS-ROM anschalten (wichtig OS-ROM muss vorher aus sein!)
          jmp (vimirq)  ; Ins Betriebssystem springen

ex      dec portb   ; OS-ROM wieder abschalten
          pla       ; Akku holen
          rti       ; und zurück

      .endp

      ; hier steht die Displaylist

dlist   .by 112,112,112
      .by 66,0,224
      .by 130     ; hier wird ein DLI ausgelöst
:10     .by 2
      .by 130     ; hier wird ein DLI ausgelöst
:10     .by 2
      .by 2
      .by 65
      .wo dlist








      run main
AntwortZitat

Hallo Peter, ich habe gerade mal Deinen Code überflogen. Da ist mir im Definitionsbereich etwas aufgefallen:

srtimr = $228

Ich finde dazu eine andere Adresse im Profibuch: $22B

PS: Ich habe soeben noch etwas festgestellt.

Damit es auch wirklich immer sauber funktioniert mit dem Abschalten sollte noch vor das erste SEI folgendes kommen:

  lda:cmp:req $14   ;wait 1 frame

gleichbedeutend mit diesem loop hier:

  lda $14
lp  cmp $14
  beq lp

Wartet man nicht kann es zu (für mich zunächst) mysteriösen Abstürzen kommen. Ich hatte z.B. das Problem, dass mein Code nur mit dem ATARI OS funktionierte und das selbe Programm mit ALTIRRA OS regelmäßig den Start versagte.

PP´s of STARSOFTBerlin____github|meine Webseite|Demozoo

AntwortZitat

Vielen Dank für die Ergänzung. Da hatte ich wohl einen Tippfehler... wenn ich hier Codes reinstelle, dann versuche ich das immer so zu machen, daß man den Code einfach kopieren kann und es läuft. Dazu muss man ja die entsprechenden Hardwareadressen, die man braucht direkt in den Code einbringen. Bei mir "in Natura" sieht das ganz anders aus. Da habe ich alle Hardwareregister in einer Datei, die ich einfach per include in meinen Code einbinde....

Das mit der kleinen Warteschleife vor SEI ist interessant, das habe ich gerade in meinem Programm zusätzlich implementiert! Danke nochmals....

AntwortZitat